// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UserInterfaceAgent.cs" company="Microsoft Corporation">
// The MIT License (MIT)
// 
// Copyright (c) 2014, Microsoft Corporation
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace Microsoft.Robotics.Visualization
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.Serialization;
    using System.Threading;
    using System.Windows;
    using Microsoft.Robotics.Runtime;

    /// <summary>
    /// Class that controls the lifecycle of the UI.
    /// It is itself an agent, so that it can be initialized/activated/deactivated by the agent framework through the same configuration mechanism as all other agents.
    /// </summary>
    [System.Diagnostics.CodeAnalysis.SuppressMessage(
    "Microsoft.Design",
    "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable",
    Justification = "Agents clean up resources when OnDeactivated() is called")]
    [DataContract]
    public class UserInterfaceAgent : Agent, IControlReceiver<SimpleAgentMessage<bool>>
    {
        /// <summary>
        /// The WPF application
        /// </summary>
        private Application application;

        /// <summary>
        /// The set of top WPF windows. Each of these contains the same set of tabs (different instances).
        /// Corresponding tabs in different windows bind to the same model object. 
        /// </summary>
        private List<MainWindow> mainWindows;

        /// <summary>
        /// The UI thread
        /// </summary>
        private Thread userInterfaceThread;

        /// <summary>
        /// The model agents corresponding to the active views. 
        /// An active agent could serve multiple views in different windows.
        /// </summary>
        private List<IAgentHost> activeModelAgents;

        /// <summary>
        /// Event used to wait for the UI thread to be fully started
        /// </summary>
        private ManualResetEvent applicationStartedEvent;

        /// <summary>
        /// The set of views to instantiate. These show up as tabs in the main window. 
        /// If multiple main windows are opened, this set is re-instantiated again in each window.
        /// </summary>
        [DataMember(Name = "Views")]
        private ViewDefinition[] viewDefinitions = null;

        /// <summary>
        /// Initializes a new instance of the UserInterfaceAgent class.
        /// </summary>
        public UserInterfaceAgent()
        {
        }

        /// <summary>
        /// Initializes a new instance of the UserInterfaceAgent class
        /// </summary>
        /// <param name="name">Agent name</param>
        /// <param name="viewDefinitions">The set of views to instantiate.</param>
        public UserInterfaceAgent(string name, ViewDefinition[] viewDefinitions)
            : base(name)
        {
            this.viewDefinitions = viewDefinitions;
        }

        /// <summary>
        /// Constructor-line initialization. 
        /// </summary>
        /// <param name="locator">The agent locator</param>
        public override void Initialize(AgentLocator locator)
        {
            base.Initialize(locator);
            this.mainWindows = new List<MainWindow>();
            this.activeModelAgents = new List<IAgentHost>();
            this.applicationStartedEvent = new ManualResetEvent(false);
        }

        /// <summary>
        /// Deactivates the model agent and stops the UI thread.
        /// </summary>
        public override void OnDeactivated()
        {
            if (this.applicationStartedEvent != null)
            {
                this.applicationStartedEvent.Dispose();
            }

            // shutdown will close the windows, which in turn will deactivate the visible (active) model agents
            if (this.application != null)
            {
                this.application.Dispatcher.Invoke(this.application.Shutdown);
                this.userInterfaceThread.Join();
            }

            // if there are any active model agents at this point (and there shouldn't be any) shut them down
            foreach (IAgentHost activeModel in this.activeModelAgents)
            {
                activeModel.Deactivate();
            }

            this.activeModelAgents.Clear();
        }

        /// <summary>
        /// Called when a window is closed. 
        /// </summary>
        /// <param name="wnd">The window being closed</param>
        public void OnMainWindowClosed(MainWindow wnd)
        {
            this.mainWindows.Remove(wnd);
        }

        /// <summary>
        /// Called when a view becomes active, to activate the associated model. This is called on the UI thread.
        /// </summary>
        /// <param name="viewName">The name of the view that became visible</param>
        /// <param name="view">The view object that needs to bind to the model provided by the agent being activated</param>
        /// <param name="window">The main window that owns the view</param>
        public void ActivateModel(string viewName, FrameworkElement view, MainWindow window)
        {
            string activeAgentName = this.viewDefinitions.First(v => v.Name == viewName).ModelAgentName;
            IAgentHost agent = this.Locator.GetAgent(activeAgentName);
            agent.Activate();

            // other views (in another windows) might have already activated this agent
            // but since we need to deactivate it as many times as it was activated, we add it to the list again
            this.activeModelAgents.Add(agent);

            // ask the agent to give the view its model
            IForwarder<ViewInitializationMessage> controlPort = agent.GetControlPort<ViewInitializationMessage>();
            controlPort.Post(new ViewInitializationMessage((FrameworkElement)view, window));
        }

        /// <summary>
        /// Called when a view becomes inactive, to deactivate the associated model. 
        /// This is called on the UI thread.
        /// </summary>
        /// <param name="viewName">The name of the view that became hidden</param>
        public void DectivateModel(string viewName)
        {
            string activeAgentName = this.viewDefinitions.First(v => v.Name == viewName).ModelAgentName;
            IAgentHost agent = this.Locator.GetAgent(activeAgentName);
            agent.Deactivate();
            this.activeModelAgents.Remove(agent);
        }

        /// <summary>
        /// Receives a control message indicating whether to show or hide the UI.
        /// </summary>
        /// <param name="message">Indicates if the UI should be shown or hidden</param>
        public void ReceiveControl(SimpleAgentMessage<bool> message)
        {
            if (message.State)
            {
                if (this.application == null)
                {
                    // first time this is called, start the UI thread
                    this.userInterfaceThread = new Thread(this.InitializeApplication);
                    this.userInterfaceThread.Name = "UserInterfaceThread";
                    this.userInterfaceThread.SetApartmentState(ApartmentState.STA);
                    this.userInterfaceThread.Start();
                    this.applicationStartedEvent.WaitOne();
                }

                // create another window
                this.application.Dispatcher.Invoke(this.CreateMainWindow);
            }
            else
            {
                // close the last opened window
                MainWindow wnd = this.mainWindows.Last();
                this.application.Dispatcher.Invoke(wnd.Close);
                this.mainWindows.Remove(wnd);
            }
        }

        /// <summary>
        /// Initialize the application. This method does not return during the life of the application.
        /// </summary>
        private void InitializeApplication()
        {
            this.application = new Application();
            this.application.Startup += (o, eventArgs) => this.applicationStartedEvent.Set();
            this.application.ShutdownMode = ShutdownMode.OnExplicitShutdown;
            this.application.Run();
        }

        /// <summary>
        /// Creates and shows a new window, and populates it with a new view instance for each view definition.
        /// </summary>
        private void CreateMainWindow()
        {
            MainWindow newWindow = new MainWindow(this);
            this.mainWindows.Add(newWindow);

            // show the window
            newWindow.Show();

            // add the tabs
            if (this.viewDefinitions != null)
            {
                foreach (ViewDefinition viewDefinition in this.viewDefinitions.Reverse())
                {
                    // make sure the name is unique
                    if (this.viewDefinitions.Count(v => v.Name == viewDefinition.Name) > 1)
                    {
                        throw new Exception(string.Format("There is more than one view with the name {0}", viewDefinition.Name)); 
                    }

                    // add the view. An exception here usually indicates an issue with the manifest.
                    Type type = Type.GetType(viewDefinition.TypeName);
                    FrameworkElement view = (FrameworkElement)Activator.CreateInstance(type);

                    // display the view
                    if (viewDefinition.IsStatusBarControl)
                    {
                        // status bar view, e.g. the pause/resume playback bar
                        newWindow.AddStatusBarControl(view, viewDefinition.Name);
                    }
                    else
                    {
                        // regular tab view
                        newWindow.AddView(view, viewDefinition.Name);
                    }
                }
            }
        }
    }
}
